package com.yang.proxy.dynamicproxy.myjdk;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Objects;

/**
 * @author yang
 * @version 0.1
 * @date 2019/4/15
 */
public class MyProxy {

    private static final String LN = "/r/n";

    public static Object newProxyInstance(MyClassLoader loader,
                                          Class<?>[] interfaces,
                                          MyInvocationHandler h) throws IOException {
        Objects.requireNonNull(h);

        String s = generateJava(interfaces);

        String path = MyProxy.class.getResource(" ").getPath();
        File file = new File(path, "$MyProxy0.java");
        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write(s);
        fileWriter.flush();
        fileWriter.close();

        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager standardFileManager = javaCompiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> iterable = standardFileManager.getJavaFileObjects(file);
        JavaCompiler.CompilationTask task = javaCompiler.getTask(null, standardFileManager, null, null, null, null);
        task.call();
        standardFileManager.close();

        try {
            Class<?> proxy0 = loader.findClass("$MyProxy0");
            return proxy0.getDeclaredConstructor(MyInvocationHandler.class).newInstance(h);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return null;
    }

    private static String generateJava(Class<?>[] interfaces) {
        StringBuffer sb = new StringBuffer();
        sb
                .append("package com.yang.proxy.dynamicproxy.myjdk;").append(LN)
                .append("import java.lang.reflect.*;").append(LN)
                .append("public class $MyProxy0 implements ");
        for (Class<?> anInterface : interfaces) {
            sb.append(anInterface.getName()).append(",");
        }
        if (sb.lastIndexOf(",") != -1) {
            sb.deleteCharAt(sb.lastIndexOf(","));
        }
        sb.append(" {").append(LN);
        sb.append("private MyInvocationHandler h;");
        sb.append("public $MyProxy0(MyInvocationHandler h) {this.h = h;}");

        for (Class<?> anInterface : interfaces) {
            Method[] methods = anInterface.getMethods();
            for (Method method : methods) {
                sb.append("public ").append(method.getReturnType()).append(" ").append(method.getName()).append("(");
                Parameter[] parameters = method.getParameters();
                for (Parameter parameter : parameters) {
                    sb.append(parameter.getType()).append(" ").append(parameter.getName()).append(", ");
                }
                if (sb.lastIndexOf(", ") != -1) {
                    sb.deleteCharAt(sb.lastIndexOf(", "));
                }
                sb.append(") {").append(LN);

                sb.append("try {");

                sb.append("Method method=").append(anInterface.getName()).append(".class").append(".getMethod(\"").append(method.getName()).append("\", ").append("new Class[]{});").append(LN);
                sb.append("this.h.invoke(this, method, null);").append(LN);

                sb.append("} catch(Exception e) { e.printStackTrace();}");

                sb.append(LN).append("}");
            }
        }

        sb.append(LN).append("}");
        return sb.toString();
    }
}
